#include "StdAfx.h"
#include "yasli/TextOArchive.h"

#include "yasli/StringUtil.h"
#include "yasli/MemoryWriter.h"

const int TAB_WIDTH = 4;

TextOArchive::TextOArchive(int textWidth, const char* header)
: Archive(false)
, header_(header)
, textWidth_(textWidth)
, open_(false)
{
    buffer_ = new MemoryWriter(1024, true);
    ASSERT(stack_.empty());
    stack_.push_back(Level(false, 0, 0));
}

TextOArchive::~TextOArchive()
{
    close();
}

void TextOArchive::open(const char* fileName)
{

    if(header_ && strlen(header_))
        (*buffer_) << header_;
    else{
        (*buffer_) << "# Autogenerated file. Edit with care.\n";
        (*buffer_) << "# vim:textwidth="  << textWidth_ << " tabstop=2:\n\n";
    }

    open_ = true;
    fileName_ = fileName;
}

void TextOArchive::close()
{
    if(open_){
        ASSERT(!fileName_.empty());
        ASSERT(stack_.size() == 1);
        stack_.pop_back();
        if(FILE* file = fopen(fileName_.c_str(), "wb")){
            ASSERT(buffer_);
            ASSERT(buffer_->position() <= buffer_->size());
            fwrite(buffer_->c_str(), buffer_->position(), 1, file);
            fclose(file);
        }
        else
            RAISE(ErrorRuntime((MemoryWriter() << "Unable to open file: " << fileName_.c_str()).c_str()));
        open_ = false;
    }
}

const char* TextOArchive::c_str() const
{
    return buffer_->c_str();
}

size_t TextOArchive::size() const
{
	return buffer_->size();
}

void TextOArchive::openBracket()
{
	*buffer_ << "{\n";
}

void TextOArchive::closeBracket()
{
	*buffer_ << "}\n";
}

void TextOArchive::openContainerBracket()
{
    *buffer_ << "[\n";
}

void TextOArchive::closeContainerBracket()
{
    *buffer_ << "]\n";
}

void TextOArchive::placeName(const char* name)
{
    if(name[0] != '\0'){
        *buffer_ << name;
        *buffer_ << " = ";
    }
}

void TextOArchive::placeIndent()
{
    int count = stack_.size() - 1;
    stack_.back().indentCount += count/* * TAB_WIDTH*/;
    for(int i = 0; i < count; ++i)
        *buffer_ << "\t";
}

bool TextOArchive::operator()(bool& value, const char* name)
{
    placeIndent();
    placeName(name);
    *buffer_ << (value ? "true" : "false");
    *buffer_ << "\n";
    return true;
}


bool TextOArchive::operator()(std::string& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << "\""; 
    const char* str = value.c_str();
    escapeString(*buffer_, str, str + value.size());
    (*buffer_) << "\"\n";
    return true;
}

bool TextOArchive::operator()(float& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(double& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(int& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(unsigned int& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(__int64& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(unsigned char& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(signed char& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(char& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << value << "\n";
    return true;
}

bool TextOArchive::operator()(StringListStaticValue& value, const char* name)
{
    placeIndent();
    placeName(name);
    (*buffer_) << "\"" << value.c_str() << "\"\n";
    return true;
}

bool TextOArchive::operator()(const Serializer& ser, const char* name)
{
    placeIndent();
    placeName(name);
    std::size_t position = buffer_->position();
    openBracket();
    stack_.push_back(Level(false, position, strlen(name) + 2 * (name[0] & 1) + (stack_.size() - 1) * TAB_WIDTH + 2));

    ASSERT(ser);
    ser(*this);

    bool joined = joinLinesIfPossible();
    stack_.pop_back();
    if(!joined)
        placeIndent();
    //else
    //    *buffer_ << " ";
    closeBracket();
    return true;
}


bool TextOArchive::operator()(const ContainerSerializationInterface& ser, const char* name)
{
    placeIndent();
    placeName(name);
    std::size_t position = buffer_->position();
    openContainerBracket();
    stack_.push_back(Level(false, position, strlen(name) + 2 * (name[0] & 1) + (stack_.size() - 1) * TAB_WIDTH + 2));

    std::size_t size = ser.size();
    for(std::size_t i = 0; i < size; ++i)
        ser(*this, i);

    bool joined = joinLinesIfPossible();
    stack_.pop_back();
    if(!joined)
        placeIndent();
    closeContainerBracket();
    return true;
}

static char* joinLines(char* start, char* end)
{
    ASSERT(start <= end);
    char* next = start;
    while(next != end){
        if(*next != '\t'){
            if(*next != '\n')
                *start = *next;
            else
                *start = ' ';
            ++start;
        }
        ++next;
    }
    return start;
}

bool TextOArchive::joinLinesIfPossible()
{
    ASSERT(!stack_.empty());
    std::size_t startPosition = stack_.back().startPosition;
    ASSERT(startPosition < buffer_->size());
    int indentCount = stack_.back().indentCount;
    //ASSERT(startPosition >= indentCount);
    if(buffer_->position() - startPosition - indentCount < std::size_t(textWidth_)){
        char* buffer = (char*)buffer_->buffer();
        char* start = buffer + startPosition;
        char* end = buffer + buffer_->position();
        end = joinLines(start, end);
        std::size_t newPosition = end - buffer;
        ASSERT(newPosition <= buffer_->position());
        buffer_->setPosition(newPosition);
        return true;
    }
    return false;
}
